--[[
     Created by IntelliJ IDEA.
     User: 毛凯
     Date: 2017/11/27
     Time: 9:38
     参数：ARGV[1]:关键词多个逗号拼接
     参数：ARGV[2]:show_type_id逗号不能为空
     参数：ARGV[3]:set_id逗号不能为空
     参数：ARGV[4]:排序   1.按时间排(默认desc)  2.按热度排  3.order排序 4.先按order排序在按update排序
     参数：ARGV[5]:pageNum 页码 0开始
     参数：ARGV[6]:pageSize 页容量
     参数：ARGV[7]:tmplTypes  0 代表普通模板, 10 代表 vip 模板
     参数：ARGV[8]:durations  1 代表0-15  2代表30-60 3代表60-90
     参数：ARGV[9]:industrySetIds  行业分类
     参数：ARGV[10]:size  尺寸分类
     返回：返回数组：
            0:code,0表示成功，其它表示没搜索到结果中途退出
            1:result，搜索结果
            2:count，总页数
            3:desc, 测试用调试字段
--]]

-- ########################################################
-- 统一模板的前缀uni_item_id
local PREFIX_UNI = "uii_"
-- 分类的前缀
local PREFIX_TYPE = "type_id_"
-- 热度排序的key
local KEY_HOT_SORT = "hot_sort"
-- 时间排序的key
local KEY_DATE_SORT = "date_sort"
-- order排序的key
local KEY_ORDER_SORT = "order_sort"
-- set_id的前缀
local PREFIX_SET_ID = "si_"
-- size_id的前缀
local PREFIX_SIZE_ID = "size_"
-- keyword的前缀
local PREFIX_KEYWORD = "kwd_"
-- 更新时间排序的key
local KEY_UPD_SORT = "upd_sort"
-- 普通模板的集合key
local NORMAL_TMPL = "vip_0"
-- vip模板的集合key
local VIP_TMPL = "vip_10"
-- 存储所有线上模板key
local ALL_TMPL = "all_tmpl"
-- 视频模板的长度集合key1  0-15秒
local VIDEO_DURATION_1 = "duration_0_15"
-- 视频模板的长度集合key3  31-60
local VIDEO_DURATION_3= "duration_31_60"
-- 视频模板的长度集合key4  61-90
local VIDEO_DURATION_4= "duration_61_90"


--用户搜索过的 key, ARGV[1]关键词  ARGV[2]type_id   ARGV[3]set_id  ARGV[4]排序字段
local RESULT_K_T_S_S = "RST_"..ARGV[1].."_"..ARGV[2].."_"..ARGV[3].."_" ..ARGV[4].."_"..ARGV[5].."_"..ARGV[7].."_"..ARGV[8].."_"..ARGV[9].."_"..ARGV[10]

-- key的连接符号
local PREFIX_S = "*"
-- 数组字符串的分割符号
local PREFIX_ARRAY_SPLIT = ","
--最大分类长度
local MAX_SORTED_SET_LEN = 100



-- #######################华丽的分割线#################################
function string.split(str, delimiter)
     if str==nil or str=='' or delimiter==nil then
          return nil
     end

     local result = {}
     for match in (str..delimiter):gmatch("(.-)"..delimiter) do
          table.insert(result, match)
     end
     return result
end
-- ######################华丽的分割线##################################
-- 组成返回值数组，code成功值，result搜索结果，count个是总页数,desc调试使用字段
local retData = function(code,result,count, desc)
     local ret = {}
     table.insert(ret, code)

     if(result==nil)
     then
          result='[]'
     end
     table.insert(ret, result)

     if(count==nil)
     then
          count=0
     end
     table.insert(ret, count)

     if(desc==nil)
     then
          desc=''
     end
     table.insert(ret, desc)
     return ret
end


-- #####################华丽的分割线###################################
-- 动态参数调用redis.call
local dynamicParaCall = function(cmd,key,array)
     -- https://stackoverflow.com/questions/42291690/redis-lua-dynamic-number-of-keys-in-redis-call
     local args = {cmd, key}
     local len = #array
     for i= 1, len do
          local key = array[i]
          table.insert(args, key)
     end
     -- unpack the table and pass to redis.pcall
     redis.call(unpack(args))
end

-- ##################华丽的分割线######################################
-- 动态参数调用redis.call,向数组中增加0
local dynamicParaCallZAddRange = function(key,array,start_i,end_i)
     -- https://stackoverflow.com/questions/42291690/redis-lua-dynamic-number-of-keys-in-redis-call
     local args = {'zadd', key}
     local len = #array
     for i= start_i, end_i do
          local mem = array[i]
          table.insert(args, 0)
          table.insert(args, mem)
     end
     -- unpack the table and pass to redis.pcall
     redis.call(unpack(args))
end

-- ####################华丽的分割线####################################
-- 动态参数调用redis.call,向数组中增加0
local dynamicParaCallZAddEx = function(key,array)
     -- https://stackoverflow.com/questions/42291690/redis-lua-dynamic-number-of-keys-in-redis-call
     local args = {'zadd', key}
     local len = #array
     local count = math.floor((len+MAX_SORTED_SET_LEN-1)/MAX_SORTED_SET_LEN)
     for i= 1, count do
          local start_i =  (i-1)*MAX_SORTED_SET_LEN+1
          local end_i =  i*MAX_SORTED_SET_LEN
          if end_i > len then
               end_i = len;
          end
          dynamicParaCallZAddRange(key,array,start_i, end_i)
     end
end

-- ###################华丽的分割线#####################################
--提前返回如果redis存储的集合中没有该集合,说明查询的结果交集肯定为空这种情况提前返回,降低消耗
--判断存储vip集合的key存不存在
local v_1 = tonumber(redis.call("exists",NORMAL_TMPL)) or 0
local v_2 = tonumber(redis.call("exists",VIP_TMPL)) or 0
--判断存储duration集合的key存不存在
local video_1 = tonumber(redis.call("exists",VIDEO_DURATION_1)) or 0
local video_3 = tonumber(redis.call("exists",VIDEO_DURATION_3)) or 0
local video_4 = tonumber(redis.call("exists",VIDEO_DURATION_4)) or 0



-- ###################华丽的分割线#####################################
-- 返回结果的方法
local returnFunction = function(cmd,key,page_num,page_size)
     local R_len = redis.call('ZCARD',key)
     --计算总页数
     local count = math.floor((R_len+page_size-1)/page_size)
     local start_num = page_num*page_size
     local end_num = start_num+page_size-1
     --按参数排序
     local pageIds = redis.call(cmd,key,start_num,end_num)

     --提取当前页数据
     local pageIdsLen = #pageIds
     local result='['
     for i= 1, pageIdsLen do
          local one_key = pageIds[i]
          local one_result = redis.call('hget',one_key, 'search_result')
          if(i>1)
          then
               -- 拼接符号
               result = result..PREFIX_ARRAY_SPLIT
          end
          result = result..one_result
     end
     local result=result..']'
     --返回搜索结果
     return retData(0,result,count,nil)
end

-- ####################华丽的分割线####################################
--获取排序的规则，默认1.时间排序    2.热度排序  3.按order排序
local sort = tonumber(ARGV[4]) or 1
-- 计算总页数ARGV[5]页码，ARGV[6]页容量
local page_num = tonumber(ARGV[5]) or 0
local page_size = tonumber(ARGV[6]) or 0


--######################华丽的分割线##################################
-- 先查找redis用户搜索过的结果中有没有  redis方法ZREVRANGE从大到小取,ZRANGE从小到大取
local res = redis.call("exists",RESULT_K_T_S_S)
if(res == 1)
then
     --如果有重新更新缓存过期时间
     redis.call("expire",RESULT_K_T_S_S,86400*1)
     -- 时间倒序大到小取
     if(sort == 1)
     then
          return returnFunction('ZREVRANGE',RESULT_K_T_S_S,page_num,page_size)
     end
     -- 热度倒序大到小取
     if(sort == 2)
     then
          return returnFunction('ZREVRANGE',RESULT_K_T_S_S,page_num,page_size)
     end
     -- order排序小到大取
     if(sort == 3)
     then
          return returnFunction('ZRANGE',RESULT_K_T_S_S,page_num,page_size)
     end
     -- 按order排序完后按更新时间排序小到大取
     if(sort == 4)
     then
          return returnFunction('ZRANGE',RESULT_K_T_S_S,page_num,page_size)
     end

end
-- ######################华丽的分割线##################################
-- A表存的是最后的交集,B表存的是临时并集
local A='A'
local B='B'
-- 删除旧数据
redis.call('del',A)
redis.call('del',B)

-- #####################华丽的分割线###################################
-- 关键字数组ARGV[1] keywords为空？
local no_kwd_flg=0
local keywords = ARGV[1]
local kwd_len = #keywords
if(keywords==nil or kwd_len < 1)
then
     no_kwd_flg=1
else
     -- 如果有keyword 有拆分数组取数组长度
     keywords = string.split(keywords, PREFIX_ARRAY_SPLIT)
     kwd_len = #keywords
     if(kwd_len==0)
     then
          no_kwd_flg=1
     end
end

-- 循环取出所有关键词的并集存到redis临时A表
if(no_kwd_flg==0)
then
     local array = {}
     for i=1, kwd_len do
          local key1 = PREFIX_KEYWORD..PREFIX_S..string.upper(keywords[i])..PREFIX_S
          local l_keys = redis.call('keys',key1)
          local l_len = #l_keys
          if(l_len < 1)
          then
               -- 内部数据错误：keywords数据错误
               return retData(1,nil,nil,'失败用户输入关键词数据并集空')
          end
          for i=1, l_len do
               table.insert(array,l_keys[i])
          end
     end
     -- 把table封装到redis
     dynamicParaCall('SUNIONSTORE', A, array)
else
     --如果没有传关键词把则取所有关键词的并集
     -- 把table封装到redis
     dynamicParaCall('SUNIONSTORE', A, {ALL_TMPL})

end

-- 保护代码:A没有数据则退出
local A_len = redis.call('SCARD',A)
if(A_len==0)
then
     -- 内部数据错误：types数据错误
     return retData(1,nil,nil,'失败关键词并集空')
end

-- #######################华丽的分割线#################################
-- 分类ID数组types为空？
local no_type_flg=0
-- ARGV[2] 是type_id
local types = ARGV[2]
local t_len = #types
if(types==nil or t_len < 1)
then
     no_type_flg=1
else
     types = string.split(types, PREFIX_ARRAY_SPLIT)
     t_len = #types
     if(t_len==0)
     then
          no_type_flg=1
     end
end
-- ARGV[2]：type_id   如果没有传分类则取所有的type_id的并集
if(no_type_flg==0)
then
     -- 如果ARGV[2]有值，则根据ARGV[2]筛选type_id，求筛选之后的并集B
     local array = {}
     for i= 1, t_len do
          local key1 = PREFIX_TYPE..types[i]
          table.insert(array, key1)
     end
     -- 把table封装到redis
     redis.call('del',B)
     dynamicParaCall('SUNIONSTORE', B, array)
     -- 再与A求交集
     redis.call('SINTERSTORE', A, A, B)
end

-- 保护代码:A没有数据则退出
local A_len = redis.call('SCARD',A)
if(A_len==0)
then
     -- 内部数据错误：types数据错误
     return retData(1,nil,nil,'失败type并集空')
end

-- #######################华丽的分割线#################################
-- 分类ID数组sets为空？
local no_set_flg=0
-- ARGV[3] 是set_id
local sets = ARGV[3]
local s_len = #sets
if(sets == nil or s_len < 1)
then
     no_set_flg=1
else
     sets = string.split(sets, PREFIX_ARRAY_SPLIT)
     s_len = #sets
     if(s_len==0)
     then
          no_set_flg=1
     end
end
-- ARGV[3]：set_id   如果没有传分类则取所有的set_id的并集
if(no_set_flg==0)
then
     -- 如果ARGV[3]有值，则根据ARGV[3]筛选set_id，求筛选之后的并集B
     local array = {}
     for i= 1, s_len do
          local key1 = PREFIX_SET_ID..sets[i]
          table.insert(array, key1)
     end
     -- 把table封装到redis
     redis.call('del',B)
     dynamicParaCall('SUNIONSTORE', B, array)
     -- 再与A求交集
     redis.call('SINTERSTORE', A, A, B)
end

-- 保护代码:A没有数据则退出
local A_len = redis.call('SCARD',A)
if(A_len==0)
then
     -- 内部数据错误：sets数据错误
     return retData(1,nil,nil,'失败set并集空')
end


-- ####################华丽的分割线####################################
-- tmplTypes标记是否需要vip模板 0普通  10vip模板
local no_tmplTypes_flg=0
-- ARGV[7] 是tmplTypes
local tmplTypes = ARGV[7]
local tt_len = #tmplTypes
if(tmplTypes == nil or tt_len < 1)
then
     no_tmplTypes_flg=1
else
     tmplTypes = string.split(tmplTypes, PREFIX_ARRAY_SPLIT)
     tt_len = #tmplTypes
     if(tt_len==0)
     then
          no_tmplTypes_flg=1
     end
end
-- ARGV[7]：tmplTypes   如果没有传分类则取所有的tmplTypes的并集
if(no_tmplTypes_flg==0)
then
     -- 如果ARGV[7]有值，则根据ARGV[7]筛选tmplTypes，求筛选之后的并集B
     local array = {}
     for i= 1, tt_len do
          local key1 = 'vip_'..tmplTypes[i]
          table.insert(array, key1)
     end
     -- 把table封装到redis
     redis.call('del',B)
     dynamicParaCall('SUNIONSTORE', B, array)
     -- 再与A求交集
     redis.call('SINTERSTORE', A, A, B)
end

-- 保护代码:A没有数据则退出
local A_len = redis.call('SCARD',A)
if(A_len==0)
then
     -- 内部数据错误：tmplTypes数据错误
     return retData(1,nil,nil,'失败tmplTypes并集空')
end

-- ####################华丽的分割线####################################
-- durations标记视频长度筛选
local no_durations_flg=0
-- ARGV[8] 是durations
local durations = ARGV[8]
local dur_len = #durations
if(durations == nil or dur_len < 1)
then
     no_durations_flg=1
else
     durations = string.split(durations, PREFIX_ARRAY_SPLIT)
     dur_len = #durations
     if(dur_len==0)
     then
          no_durations_flg=1
     end
end
-- ARGV[8]：durations   如果没有传分类则取所有的durations的并集
if(no_durations_flg==0)
then
     -- 如果ARGV[8]有值，则根据ARGV[8]筛选durations，求筛选之后的并集B
     local array = {}
     for i= 1, dur_len do
          if(video_1 > 0 and tonumber(durations[i]) == 1)
          then
               table.insert(array, VIDEO_DURATION_1)
          end
          if(video_3 > 0 and tonumber(durations[i]) == 2)
          then
               table.insert(array, VIDEO_DURATION_3)
          end
          if(video_4 > 0 and tonumber(durations[i]) == 3)
          then
               table.insert(array, VIDEO_DURATION_4)
          end
     end
     -- 把table封装到redis
     redis.call('del',B)
     dynamicParaCall('SUNIONSTORE', B, array)
     -- 再与A求交集
     redis.call('SINTERSTORE', A, A, B)
end

-- 保护代码:A没有数据则退出
local A_len = redis.call('SCARD',A)
if(A_len==0)
then
     -- 内部数据错误：durations数据错误
     return retData(1,nil,nil,'失败durations并集空')
end


-- #######################华丽的分割线#################################
-- 行业ID数组为空
local no_industry_flg=0
-- ARGV[9] 是industry_ids行业ids
local industry = ARGV[9]
local industry_len = #industry
if(industry == nil or industry_len < 1)
then
     no_industry_flg=1
else
     industry = string.split(industry, PREFIX_ARRAY_SPLIT)
     industry_len = #industry
     if(industry_len==0)
     then
          no_industry_flg=1
     end
end
-- ARGV[9] 是industry行业ids
if(no_industry_flg==0)
then
     -- 如果ARGV[9]有值，则根据ARGV[9]筛选industryIds，求筛选之后的并集B
     local array = {}
     for i= 1, industry_len do
          local key1 = PREFIX_SET_ID..industry[i]
          table.insert(array, key1)
     end
     -- 把table封装到redis
     redis.call('del',B)
     dynamicParaCall('SUNIONSTORE', B, array)
     -- 再与A求交集
     redis.call('SINTERSTORE', A, A, B)
end
-- 保护代码:A没有数据则退出
local A_len = redis.call('SCARD',A)
if(A_len==0)
then
     -- 内部数据错误：industry数据错误
     return retData(1,nil,nil,'失败行业并集空')
end

-- #######################华丽的分割线#################################
-- 尺寸ID数组为空
local no_size_flg=0
-- ARGV[10] 是sizeIds尺寸ids
local sizeIds = ARGV[10]
local size_len = #sizeIds
if(sizeIds == nil or size_len < 1)
then
     no_size_flg = 1
else
     sizeIds = string.split(sizeIds, PREFIX_ARRAY_SPLIT)
     size_len = #sizeIds
     if(size_len==0)
     then
          no_size_flg = 1
     end
end
-- ARGV[10] 是sizeIds
if(no_size_flg == 0)
then
     -- 如果ARGV[10]有值，则根据ARGV[10]筛选size，求筛选之后的并集B
     local array = {}
     for i= 1, size_len do
          local key1 = PREFIX_SIZE_ID..sizeIds[i]
          table.insert(array, key1)
     end
     -- 把table封装到redis
     redis.call('del',B)
     dynamicParaCall('SUNIONSTORE', B, array)
     -- 再与A求交集
     redis.call('SINTERSTORE', A, A, B)
end



-- 保护代码:A没有数据则退出
local A_len = redis.call('SCARD',A)
if(A_len==0)
then
     -- 内部数据错误：sizeIds数据错误
     return retData(1,nil,nil,'失败尺寸并集空')
end

-- ####################华丽的分割线####################################

local ids = redis.call('SMEMBERS',A)
--清除A,B,C,D,E临时表
redis.call('del',A)
redis.call('del',B)
--A表中最后的结果字段ids给B表
dynamicParaCallZAddEx(B, ids)

--默认排序方式倒序
local sort_cmd = 'ZREVRANGE';
-- 按时间排序
if(sort == 1)
then
     redis.call('ZINTERSTORE',RESULT_K_T_S_S,2,B,KEY_DATE_SORT)
end
-- 按热度排序
if(sort == 2)
then
     redis.call('ZINTERSTORE',RESULT_K_T_S_S,2,B,KEY_HOT_SORT)
end
-- 按order排序从小到大
if(sort == 3)
then
     redis.call('ZINTERSTORE',RESULT_K_T_S_S,2,B,KEY_ORDER_SORT)
     --正序排序
     sort_cmd = 'ZRANGE';
end
-- 先按order排序在按更新时间排序
if(sort == 4)
then
     redis.call('ZINTERSTORE',RESULT_K_T_S_S,2,B,KEY_UPD_SORT)
     --正序排序
     sort_cmd = 'ZRANGE';
end

--设置用户搜索过的key过期时间
redis.call("expire",RESULT_K_T_S_S,3600*1)
return returnFunction(sort_cmd,RESULT_K_T_S_S,page_num,page_size)
